//! 合约消息
//!

use crate::{
    bean::message::{base_message::BaseMessage, fee, message_type::MessageType},
    core::constant,
    error::{error_msg, Error},
    mnemonic::Mnemonic,
    utils::sign,
};
use base64::{engine::general_purpose::STANDARD, Engine};
use crypto::{
    digest::Digest,
    sha3::{Sha3, Sha3Mode},
};
use hdwallet::secp256k1::Secp256k1;
use num_bigint::BigInt;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};

#[derive(Debug, Clone, Default)]
pub struct TransactionMessage {
    base_message: BaseMessage,
    // 接收地址
    to_address: String,
    // 交易消息内容hash值
    hash: String,
    // 交易金额
    amount: BigInt,
    // 交易手续费
    fee: BigInt,
    // NRG价格
    nrg_price: BigInt,
    // 交易附言
    remark: String,
    // 交易ID(构建交易时不需要，但是内部解析入库前需要用)
    id: BigInt,
    // 所属打包Event的hash值
    e_hash: String,
}

impl Deref for TransactionMessage {
    type Target = BaseMessage;

    fn deref(&self) -> &Self::Target {
        &self.base_message
    }
}

impl DerefMut for TransactionMessage {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base_message
    }
}

impl TransactionMessage {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn new_(
        words: &str,
        passphrase: &str,
        from_address: &str,
        to_address: &str,
        amount: &BigInt,
        fee: &BigInt,
        nrg_price: &BigInt,
        remark: &str,
    ) -> Result<Self, Error> {
        if words.is_empty() || words.split(" ").count() != constant::MNEMONIC_WORD_SIZE {
            return Err(error_msg("words is illegal"));
        }

        if from_address.is_empty() {
            return Err(error_msg("from_address is illegal"));
        }

        if to_address.is_empty() {
            return Err(error_msg("to_address is illegal"));
        }
        if amount <= &0.into() {
            return Err(error_msg("amount is illegal"));
        }

        let mut new_one = Self::default();
        new_one.set_msg_type(MessageType::Transactions);
        new_one.set_from_address(from_address);
        new_one.set_to_address(to_address);
        new_one.set_amount(amount);
        new_one.set_remark(remark);
        if constant::NEED_FEE {
            let calc_fee = fee::calculate_fee_by_len(remark.len() as i64);
            let tmp_fee = if fee > &calc_fee { fee } else { &calc_fee };
            new_one.set_fee(tmp_fee);
            new_one.set_nrg_price(nrg_price);
        }

        new_one.set_timestamp(chrono::Local::now().timestamp());
        new_one.sign_message(words, passphrase)?;
        Ok(new_one)
    }

    fn sign_message(&mut self, words: &str, passphrase: &str) -> Result<(), Error> {
        let mnemonic = Mnemonic::new(words, passphrase)?;
        let keys = mnemonic.keypair(44, 0, 0, 0, 0.into())?;
        let private_key = keys.secret_key();
        let pub_key = STANDARD.encode(private_key.public_key(&Secp256k1::new()).serialize());
        self.set_pub_key(&pub_key);
        let message = self.get_message()?;
        self.set_signature(&sign::sign(&message, &private_key)?);
        let mut sha = Sha3::new(Sha3Mode::Sha3_256);
        let mut result = [0u8; 32];
        sha.input(&STANDARD.decode(&self.signature()[4..])?);
        sha.result(&mut result);
        self.hash = STANDARD.encode(result);
        Ok(())
    }

    pub fn get_message(&self) -> Result<String, Error> {
        let mut inner = Inner::default();
        if self.from_address().is_empty() {
            return Err(error_msg("from_address is illegal"));
        }
        inner.from_address = self.from_address().to_string();

        if self.to_address().is_empty() {
            return Err(error_msg("to_address is illegal"));
        }
        inner.to_address = self.to_address().to_string();

        if self.amount() <= &0.into() {
            return Err(error_msg("amount is illegal"));
        }
        inner.amount = self.amount().into();

        let now = chrono::Local::now().timestamp();
        if now - self.timestamp() > constant::MESSAGE_PRESEND_TIME_OUT as i64
            || self.timestamp() > now
        {
            return Err(error_msg("timestamp is illegal"));
        }
        inner.timestamp = self.timestamp();

        if self.pub_key().is_empty() {
            return Err(error_msg("pubkey is illegal"));
        }
        inner.pubkey = self.pub_key().to_string();

        inner.fee = self.fee().into();
        if self.msg_type() != MessageType::Transactions {
            return Err(error_msg("type is illegal"));
        }
        inner.r#type = self.msg_type();
        inner.remark = self.remark().to_string();
        if self.signature().is_empty() {
            inner.signature = None;
            Ok(serde_json::to_string(&inner)?)
        } else {
            inner.signature = Some(self.signature().to_string());
            let msg = MessageJson { message: inner };
            Ok(serde_json::to_string(&msg)?)
        }
    }

    pub fn set_to_address(&mut self, to_address: &str) {
        self.to_address = to_address.to_string();
    }

    pub fn set_hash(&mut self, hash: &str) {
        self.hash = hash.to_string();
    }

    pub fn set_amount(&mut self, amount: &BigInt) {
        self.amount = amount.clone();
    }

    pub fn set_fee(&mut self, fee: &BigInt) {
        self.fee = fee.clone();
    }

    pub fn set_nrg_price(&mut self, nrg_price: &BigInt) {
        self.nrg_price = nrg_price.clone();
    }

    pub fn set_remark(&mut self, remark: &str) {
        self.remark = remark.to_string();
    }

    pub fn set_id(&mut self, id: &BigInt) {
        self.id = id.clone();
    }

    pub fn set_e_hash(&mut self, e_hash: &str) {
        self.e_hash = e_hash.to_string();
    }

    pub fn to_address(&self) -> &str {
        &self.to_address
    }

    pub fn hash(&self) -> &str {
        &self.hash
    }

    pub fn amount(&self) -> &BigInt {
        &self.amount
    }

    pub fn fee(&self) -> &BigInt {
        &self.fee
    }

    pub fn nrg_price(&self) -> &BigInt {
        &self.nrg_price
    }

    pub fn remark(&self) -> &str {
        &self.remark
    }

    pub fn id(&self) -> &BigInt {
        &self.id
    }

    pub fn e_hash(&self) -> &str {
        &self.e_hash
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MessageJson {
    message: Inner,
}
use crate::core::event_body::BigInt as CrateBigInt;

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Inner {
    from_address: String,
    to_address: String,
    amount: CrateBigInt,
    timestamp: i64,
    pubkey: String,
    fee: CrateBigInt,
    r#type: MessageType,
    remark: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    signature: Option<String>,
}

#[cfg(test)]
mod tests {
    use crate::{bean::message::transaction_message::TransactionMessage, mnemonic::Mnemonic};

    #[test]
    fn test_transaction() {
        let mnemonic = Mnemonic::new("", "hello").unwrap();
        let words = mnemonic.mnemonic();
        println!("words: {}", words);
        let tm = TransactionMessage::new_(
            words,
            "haha",
            "192.168.1.1",
            "192.168.1.0",
            &10.into(),
            &50.into(),
            &10000.into(),
            "花费",
        )
        .unwrap();
        println!("tm: {:?}", tm);
        let json = tm.get_message().unwrap();
        println!("json: {}", json);
    }
}
